// Copyright (c) The Diem Core Contributors
// Copyright (c) The Move Contributors
// SPDX-License-Identifier: Apache-2.0

use move_binary_format::{
    binary_config::BinaryConfig,
    file_format::{Bytecode, CompiledModule},
};
use proptest::prelude::*;

proptest! {
    #[test]
    fn serializer_roundtrip(module in CompiledModule::valid_strategy(30)) {
        let mut serialized = vec![];
        module.serialize(&mut serialized).expect("serialization should work");

        // Metadata may be generated as part of the proptest, so we need to allow extra bytes.
        let config = BinaryConfig::legacy_with_flags(
            /* check_no_extraneous_bytes */ false, /* deprecate_global_storage_ops */ false,
        );

        let deserialized_module = CompiledModule::deserialize_with_config(&serialized, &config)
            .expect("deserialization should work");

        prop_assert_eq!(module, deserialized_module);
    }

    #[test]
    fn serializer_roundtrip_with_defaults(mut module in CompiledModule::valid_strategy(30)) {
        // Metadata may be generated as part of the proptest, so we need to allow extra bytes.
        module.metadata = vec![];
        // Deprecated global storage operations are not supported with default deserialization.
        // They might still be generated by the proptest, so we need to remove them.
        for fdef in &mut module.function_defs {
            let Some(code) = &mut fdef.code else { continue; };
            for instr in &mut code.code {
                match instr {
                    Bytecode::ExistsDeprecated(_)
                        | Bytecode::ExistsGenericDeprecated(_)
                        | Bytecode::MoveFromDeprecated(_)
                        | Bytecode::MoveFromGenericDeprecated(_)
                        | Bytecode::MoveToDeprecated(_)
                        | Bytecode::MoveToGenericDeprecated(_)
                        | Bytecode::MutBorrowGlobalDeprecated(_)
                        | Bytecode::MutBorrowGlobalGenericDeprecated(_)
                        | Bytecode::ImmBorrowGlobalDeprecated(_)
                        | Bytecode::ImmBorrowGlobalGenericDeprecated(_) => {
                        // Replace deprecated global storage operations with Nop.
                        *instr = Bytecode::Nop;
                    }
                    _ => (),
                }
            }
            fdef.acquires_global_resources = vec![];
        }
        let mut serialized = vec![];
        module.serialize(&mut serialized).expect("serialization should work");

        let deserialized_module = CompiledModule::deserialize_with_defaults(&serialized)
            .expect("deserialization should work");

        prop_assert_eq!(module, deserialized_module);
    }

}

proptest! {
    // Generating arbitrary compiled modules is really slow, possibly because of
    // https://github.com/AltSysrq/proptest/issues/143.
    #![proptest_config(ProptestConfig::with_cases(16))]

    /// Make sure that garbage inputs don't crash the serializer and deserializer.
    #[test]
    fn garbage_inputs(module in any_with::<CompiledModule>(16)) {
        let mut serialized = Vec::with_capacity(65536);
        module.serialize(&mut serialized).expect("serialization should work");

        let deserialized_module = CompiledModule::deserialize_no_check_bounds(&serialized)
            .expect("deserialization should work");
        prop_assert_eq!(module, deserialized_module);
    }
}
